// Copyright (c) Aptos Foundation
// Licensed pursuant to the Innovation-Enabling Source Code License, available at https://github.com/aptos-labs/aptos-core/blob/main/LICENSE

use aptos_protos::indexer::v1::GetTransactionsRequest;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tonic::Request;
use uuid::Uuid;

// Maximum number of threads for the file store
pub const MAXIMUM_NUMBER_FILESTORE_THREADS: usize = 10;
// GRPC request metadata key for the token ID.
pub const GRPC_AUTH_TOKEN_HEADER: &str = "x-aptos-data-authorization";
// GRPC request metadata key for the request name. This is used to identify the
// data destination.
pub const GRPC_REQUEST_NAME_HEADER: &str = "x-aptos-request-name";
pub const GRPC_API_GATEWAY_API_KEY_HEADER: &str = "authorization";
// Limit the message size to 15MB. By default the downstream can receive up to 15MB.
pub const MESSAGE_SIZE_LIMIT: usize = 1024 * 1024 * 15;

// These come from API Gateway, see here:
// https://github.com/aptos-labs/api-gateway/blob/0aae1c17fbd0f5e9b50bdb416f62b48d3d1d5e6b/src/common.rs

/// The type of the auth identity. For example, "anonymous IP" or "application" (API
/// key). For now all data service connections must be from an application, but we
/// include this for future-proofing.
pub const REQUEST_HEADER_APTOS_IDENTIFIER_TYPE: &str = "x-aptos-identifier-type";
/// The identifier uniquely identifies the requester. For an application, this is the
/// application ID, a UUID4.
pub const REQUEST_HEADER_APTOS_IDENTIFIER: &str = "x-aptos-identifier";
/// The email of the requester. For an application, this is the email of the user who
/// created the application. When looking at metrics based on this label, you should
/// also parallelize based on the application name. Or just use the identifier.
pub const REQUEST_HEADER_APTOS_EMAIL: &str = "x-aptos-email";
/// The name of the application, e.g. something like "Graffio Testnet".
pub const REQUEST_HEADER_APTOS_APPLICATION_NAME: &str = "x-aptos-application-name";

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct IndexerGrpcRequestMetadata {
    pub processor_name: String,
    /// See `REQUEST_HEADER_APTOS_IDENTIFIER_TYPE` for more information.
    pub request_identifier_type: String,
    /// See `REQUEST_HEADER_APTOS_IDENTIFIER` for more information.
    pub request_identifier: String,
    /// See `REQUEST_HEADER_APTOS_EMAIL` for more information.
    pub request_email: String,
    /// See `REQUEST_HEADER_APTOS_APPLICATION_NAME` for more information.
    pub request_application_name: String,
    pub request_connection_id: String,
    // Token is no longer needed behind api gateway.
    #[deprecated]
    pub request_token: String,
}

impl IndexerGrpcRequestMetadata {
    /// Get the label values for use with metrics that use these labels. Note, the
    /// order must match the order in metrics.rs.
    pub fn get_label_values(&self) -> Vec<&str> {
        vec![
            &self.request_identifier_type,
            &self.request_identifier,
            &self.request_email,
            &self.request_application_name,
            &self.processor_name,
        ]
    }
}

/// Gets the request metadata from gRPC request headers. Useful for logging and metrics.
pub fn get_request_metadata(req: &Request<GetTransactionsRequest>) -> IndexerGrpcRequestMetadata {
    let request_metadata_pairs = vec![
        (
            "request_identifier_type",
            REQUEST_HEADER_APTOS_IDENTIFIER_TYPE,
        ),
        ("request_identifier", REQUEST_HEADER_APTOS_IDENTIFIER),
        ("request_email", REQUEST_HEADER_APTOS_EMAIL),
        (
            "request_application_name",
            REQUEST_HEADER_APTOS_APPLICATION_NAME,
        ),
        ("request_token", GRPC_AUTH_TOKEN_HEADER),
        ("processor_name", GRPC_REQUEST_NAME_HEADER),
    ];
    let mut request_metadata_map: HashMap<String, String> = request_metadata_pairs
        .into_iter()
        .map(|(key, value)| {
            (
                key.to_string(),
                req.metadata()
                    .get(value)
                    .map(|value| value.to_str().unwrap_or("unspecified").to_string())
                    .unwrap_or("unspecified".to_string()),
            )
        })
        .collect();
    request_metadata_map.insert(
        "request_connection_id".to_string(),
        Uuid::new_v4().to_string(),
    );

    // TODO: update the request name if these are internal requests.
    serde_json::from_str(&serde_json::to_string(&request_metadata_map).unwrap()).unwrap()
}
